package com.glacier.common.core.utils;

import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;

/**
 * date 2022-10-28 16:47
 * ip 工具类
 *
 * @author glacier
 * @version 1.0
 */
public class IpUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(IpUtils.class);
    public static final Pattern PATTERN = Pattern.compile(
            "\\b((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\b");

    private IpUtils() {
    }

    /**
     * 获取 客户端 IP 地址<BR>
     * 如果通过了多级反向代理的话，X-Forwarded-For的值并不止一个，而是一串IP值，
     * 那么真正的用户端的真实IP则是取X-Forwarded-For中第一个非unknown的有效IP字符串。
     *
     * @param request http请求
     * @return
     */
    public static String getIpFromReq(HttpServletRequest request) {
        final String unknown = "unknown";
        List<String> headers = Arrays.asList(
                "x-forwarded-for",
                "Proxy-Client-IP",
                "WL-Proxy-Client-IP",
                "HTTP_CLIENT_IP",
                "HTTP_X_FORWARDED_FOR",
                "X-Real-IP"
        );
        String ip = null;
        for (String header : headers) {
            ip = request.getHeader(header);
            if (!isBlank(ip) && !unknown.equalsIgnoreCase(ip)) {
                break;
            }
        }
        if (isBlank(ip) || unknown.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        // 多个路由时，取第一个非unknown的ip
        ip = Arrays.stream(ip.split(","))
                .filter(s -> !unknown.equalsIgnoreCase(s))
                .findFirst()
                .orElse("");

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("获取IP {}", ip);
        }
        return ip;
    }

    /**
     * <p>
     * 判断 IP 是否存在于指定规则中<BR>
     * 思路如下：
     * <p>
     * <pre>
     * 1.转换 【*-】 为 【0-】；
     * 2.转换 【-*】 为 【-255】;
     * 3.转换 【*】  为 【0-255】;
     * 4.去除 【[]】 为 【】
     * 5.提取最大、最小两个IP值;
     * 6、把 IP 转化为 Long，通过比较大小判断是否在指定规则中。
     * </pre>
     * <p>
     * <pre>
     * 用法如下：
     * <OL>
     * <LI>
     * *.*.*.*                          代表 0.0.0.0 至 255.255.255.255
     * <LI>
     * 192.168.0.*                      代表 192.168.0.0 至 192.168.0.255
     * <LI>
     * 192.168.0.[0-255]                代表 192.168.0.0 至 192.168.0.255
     * <LI>
     * 192.168.*.[0-255]                代表 192.168.0.0 至 192.168.255.255
     * <LI>
     * [0-*].[*-20].*.[0-255]           代表 0.0.0.0 至 255.20.255.255
     * <LI>
     * [0-255].[0-255].[0-255].[0-255]  代表 0.0.0.0 至 255.255.255.255
     * </pre>
     *
     * @param pattern IP规则
     * @param ip      判定IP
     * @return
     */
    public static boolean compare(String pattern, String ip) {
        if (isBlank(pattern)
                || isBlank(ip)
                || !isIPAddress(ip)) {
            return false;
        }
        // 去除空格
        pattern = pattern.replaceAll("\\s*", "");
        // 替换[- [*- 为0-
        pattern = pattern.replaceAll("\\s*", "");
        pattern = pattern.replaceAll("\\[\\**-", "0-");
        // 替换-] -*] 为-255
        pattern = pattern.replaceAll("-\\**\\]", "-255");
        // 替换* 为0-255
        pattern = pattern.replaceAll("\\*+", "0-255");
        // 替换[
        pattern = pattern.replace("[", "");
        // 替换]
        pattern = pattern.replace("]", "");

        String minIp = pattern.replaceAll("-[^\\.]*", "");
        String maxIp = pattern.replaceAll("[^\\.]*-", "");


        long ipValue = Long.parseLong(ipPad(ip).replace(".", ""));
        long minValue = Long.parseLong(ipPad(minIp).replace(".", ""));
        long maxValue = Long.parseLong(ipPad(maxIp).replace(".", ""));

        return ipValue >= minValue && ipValue <= maxValue;
    }

    public static boolean isIPAddress(String ipaddr) {
        return PATTERN.matcher(ipaddr)
                .matches();
    }

    /**
     * 补全 IP 地址<BR>
     * 把 IP: 1.1.1.1 补全为 IP: 001.001.001.001<BR>
     * 通过正则替换完成, 思路如下：
     * <p>
     * <pre>
     * 1.IP 第   一  段,长度为 1、2 位补码；
     * 2.IP 第 二、三 段,长度为 1、2 位补码；
     * 3.IP 第   四  段,长度为 1、2 位补码；
     * </pre>
     *
     * @param ip IP地址
     * @return
     */
    public static String ipPad(String ip) {
        if (isBlank(ip)) {
            return ip;
        }
        ip = ip.replaceAll("^(\\d)[.]", "00$1.").replaceAll("^(\\d\\d)[.]", "0$1.");
        ip = ip.replaceAll("[.](\\d)[.]", ".00$1.").replaceAll("[.](\\d\\d)[.]", ".0$1.");
        ip = ip.replaceAll("[.](\\d)$", ".00$1").replaceAll("[.](\\d\\d)$", ".0$1");
        return ip;
    }

    /**
     * 判断字符串是否为空
     *
     * @param str
     * @return
     */
    private static boolean isBlank(String str) {
        return str == null || str.isBlank();
    }
}
